/**
 * This class is the root of the algorithm's hierarchy. An Algorithm represents a combination strategy.
 * The algorithm contains a vector of @see Set elements, a list of @see PairsTable and a collection of integers, which represents the selected positions.
 * When the algorithm is executed from a web application, a JspWriter out object can be assigned to the algorithm. If it exists, each algorithm
 * will show the different steps of its execution.
 *  @author Macario Polo Usaola
 */

package edu.uclm.combTest.core;

import java.io.PrintStream;
import java.util.Iterator;
import java.util.Vector;
//import javax.servlet.jsp.JspWriter;

import edu.uclm.combTest.core.pairwise.Pair;
import edu.uclm.combTest.core.pairwise.PairsTable;

public abstract class Algorithm {
    protected Vector sets;
    protected /*@ nullable @*/ long[] divisors;
    // protected long maxNumberOfCombinations;
    protected OrderedVector selectedPositions;
    protected PrintStream out = System.out;
    protected /*@ nullable @*/ PairsTable[] pairsTables;

    /**
     * Builds an instance of the algorithm.
     */
    public Algorithm() {
	this.sets = new Vector();
	this.selectedPositions = new OrderedVector();
	// this.out=null;
    }

    /**
     * Sets the web page where the execution steps will be shown.
     * 
     * @param out
     *            The JspWriter object where the results will be shown
     */
    public void setOut(PrintStream out) {
	this.out = out;
    }



    /**
     * Adds a set (i.e., a parameter) to the algorithm. Each set has a set of @see
     * Element
     * 
     * @param set
     *            The set to be added
     * @see Set
     */
    public void add(Set set) {
	this.sets.add(set);
	this.calculateDivisors();
    }

    /**
     * Retourne une combinaison qui contient, par exemple, le 1er element de
     * chaques sets (parametres). <br>
     * Cela nous permet d'avoir la premiere combinaison (ex: 0,0,0)...
     * 
     * @param position
     * @return the @see Combination object corresponding to the position passed
     *         as parameter
     */
    public Combination getCombination(long position) {
	Combination result = getSizedCombination(this.sets.size());
	for (int i = 0; i < result.size(); i++) {
	    long divisor = this.divisors[i];
	    Set s = (Set) this.sets.get(i);
	    long elementPosition = (position / divisor) % s.size();
	    result.setValuePosition(i, elementPosition);
	    Element element = s.getElementAt((int) elementPosition);
	    result.setValue(i, element);
	}
	return result;
    }

    /**
     * 
     * @param size
     *            The number of elements the combination (test case) will have
     * @return A combination adequately sized (with so many elements as sets
     *         there are)
     */
    protected Combination getSizedCombination(int size) {
	return new Combination(size);
    }

    /**
     * nombre de combinaison que l'on peut réaliser. divisors[i] la taille de
     * tout les sets j tel que j>i, multiplies entre eux.
     */
    protected void calculateDivisors() {
	this.divisors = new long[this.sets.size()];
	for (int i = 0; i < this.sets.size(); i++) {
	    long divisor = 1;
	    for (int j = i + 1; j < this.sets.size(); j++) {
		Set s = (Set) this.sets.get(j);
		divisor = divisor * s.size();
	    }
	    this.divisors[i] = divisor;
	}
    }

    /**
     * @return A string with the elements in all the selected combinations
     */
    public String toString() {
	String result = "Values = {\n";
	int cont = 0;

	for (Iterator iterator = this.selectedPositions.iterator(); iterator
		.hasNext();) {
	    long sp = ((Long) iterator.next()).longValue();
	    Combination combination = this.getCombination(sp);
	    result += cont + "-> " + combination.toString() + "\n";
	    cont++;
	}

	result += "}\n";
	return result;
    }

    /**
     * Function which must be implemented by any combination strategy
     */
    public abstract void buildCombinations();

    public Vector getSelectedCombinations() {
	Vector result = new Vector();

	for (Iterator iterator = this.selectedPositions.iterator(); iterator
		.hasNext();) {
	    Long i = (Long) iterator.next();
	    result.add(this.getCombination(i.longValue()));
	}

	return result;
    }

    /**
     * Function which returns the algorithm's name
     * 
     * @return the algorithm's name
     */
    public abstract String getName();

    /**
     * 
     * @return A vector with the sets
     */
    public Vector getSets() {
	return sets;
    }

    /**
     * Returns the number of pairs visited in a set by the element passed as
     * parameter
     * 
     * @param setPosition
     *            the index of the set we are interested in counting its pairs
     * @param elementPosition
     *            the position of the element whose participation in pairs must
     *            be counted
     * @return the number of pairs visited
     */
    protected int getPairsVisited(int setPosition, int elementPosition) {
	int result = 0;
	for (int i = 0; i < this.pairsTables.length; i++) {
	    PairsTable pt = this.pairsTables[i];
	    if (pt.getIndexA() == setPosition) {
		for (Iterator iterator = pt.getPairs().iterator(); iterator
			.hasNext();) {
		    Pair p = (Pair) iterator.next();
		    if (p.weight() == 0 && p.getA() == elementPosition)
			result++;
		}
	    } else if (pt.getIndexB() == setPosition) {
		for (Iterator iterator = pt.getPairs().iterator(); iterator
			.hasNext();) {
		    Pair p = (Pair) iterator.next();
		    if (p.weight() == 0 && p.getB() == elementPosition)
			result++;
		}
	    }

	}
	return result;
    }

    /**
     * Returns the number of pairs visited by a given combination in the set
     * passed as second parameter
     * 
     * @param c
     *            the combination whose visited pairs must be counted
     * @param setPosition
     *            the position of the set
     * @return the number of pairs visited
     */
    protected int getPairsVisited(Combination c, int setPosition) {
	int pairsVisited = 0;
	for (int i = 0; i < c.size(); i++) {
	    if (c.getPosition(i) != -1) {
		if (i != setPosition) {
		    Pair p = null;
		    PairsTable pt;
		    if (i > setPosition) {
			pt = this.findPairsTable(setPosition, i);
			p = pt.getPair(c.getPosition(setPosition),
				c.getPosition(i));
		    } else if (i < setPosition) {
			pt = this.findPairsTable(i, setPosition);
			p = pt.getPair(c.getPosition(i),
				c.getPosition(setPosition));
		    }
		    if (p == null)
			return Integer.MIN_VALUE;
		    if (p.weight() == 0)
			pairsVisited++;
		}
	    }
	}
	return pairsVisited;
    }

    /**
     * Returns the pairsTable corresponding to the sets a and b
     * 
     * @param a
     *            the index of the first set involved in the pairs table
     * @param b
     *            the index of the second set involved in the pairs table
     * @return the pair table
     */
    protected PairsTable findPairsTable(long a, long b) {
	/*
	 * if (a>b) { int aux=a; a=b; b=aux; }
	 */
	for (int i = 0; i < this.pairsTables.length; i++) {
	    PairsTable pt = this.pairsTables[i];
	    if (pt.getIndexA() == a && pt.getIndexB() == b)
		return pt;
	}
	return null;
    }

    /**
     * Builds the pairs tables corresponding to the elements added to this
     * algorithm
     * 
     * @return the pairs tables
     */
    protected PairsTable[] buildPairTables() {
	int numberOfTables = this.sets.size() * (this.sets.size() - 1) / 2;
	PairsTable[] result = new PairsTable[numberOfTables];
	PairsTable auxi;
	int cont = 0;
	for (int i = 0; i < this.sets.size(); i++) {
	    for (int j = i + 1; j < this.sets.size(); j++) {
		auxi = getPairsTable(i, j);
		result[cont++] = auxi;
	    }
	}
	return result;
    }

    /**
     * Builds the pair table corresponding to the sets i and j
     * 
     * @param i
     *            the index of the first set
     * @param j
     *            the index of the second set
     * @return the pairs table
     */
    protected PairsTable getPairsTable(int i, int j) {
	Set a = (Set) this.sets.get(i);
	Set b = (Set) this.sets.get(j);
	PairsTable result = new PairsTable(a, b);
	result.setIndexA(i);
	result.setIndexB(j);
	return result;
    }



    public abstract String getCredits();
}
